import {
  Directive,
  HostBinding,
  HostListener,
  Input,
  Output,
  EventEmitter,
  ElementRef,
  Optional,
  Self,
  AfterViewInit,
  OnInit,
  OnDestroy,
} from '@angular/core';
import { asapScheduler, merge, of as observableOf, Subscription } from 'rxjs';
import { delay, filter } from 'rxjs/operators';

import { CascadeComponent } from './cascade.component';
import { CascadeItemComponent } from './cascade-item.component';

@Directive({
  selector: '[fuiCascadeTrigger]',
})
export class CascadeTriggerDirective implements OnInit, OnDestroy, AfterViewInit {
  @HostBinding('class.fui-cascade-open')
  get subCascadeOpenClass() {
    return this.subCascadeOpen;
  }

  private _subCascade: CascadeComponent;
  @Input('fuiCascadeTrigger')
  set subCascade(value: CascadeComponent) {
    if (value) {
      this._subCascade = value;

      if (this.triggersSubCascade()) {
        this.subCascade.isSubCascade = true;
      }

      /** Update open status */
      this.subCascade.visible.subscribe((visible) => {
        this.subCascadeOpen = visible;
        this.fuiCascadeVisible.emit(visible);
      });

      /** Close on subCascade being closed by click */
      this.subCascade.closed
      .subscribe((reason: string) => {
        if (reason && reason === 'click' && this.parentCascade) {
          this.parentCascade.hideCascade('click');
        }
      });

      this.subCascade.setOverlayOrigin(this);
    }
  }
  get subCascade() {
    return this._subCascade;
  }

  @Output() fuiCascadeVisible = new EventEmitter<boolean>();

  subCascadeOpen = false;
  hoverSubscription = Subscription.EMPTY;
  closingActionsSubscription = Subscription.EMPTY;

  @HostListener('click') onHostClick() {
    this.handleClick();
  }

  constructor(
    public elementRef: ElementRef,
    @Optional() private parentCascade: CascadeComponent,
    @Optional() @Self() private cascadeItemInstance: CascadeItemComponent,
  ) {
    if (this.cascadeItemInstance) {
      this.cascadeItemInstance.triggersSubCascade = this.triggersSubCascade();
    }
  }

  ngOnInit() {
  }

  ngAfterViewInit() {
    this.handleHover();
  }

  ngOnDestroy() {
    this.hoverSubscription.unsubscribe();
    this.closingActionsSubscription.unsubscribe();
  }

  /** Whether the cascade triggers a sub-cascade or a top-level one. */
  triggersSubCascade(): boolean {
    return !!(this.cascadeItemInstance && this.parentCascade);
  }

  handleClick() {
    if (this.triggersSubCascade() && this.subCascadeOpen) {
      return;
    }

    if (!this.subCascadeOpen) {
      this.showCascade();
    } else {
      this.hideCascade();
    }
  }

  handleHover() {
    if (!this.triggersSubCascade()) {
      return;
    }

    this.hoverSubscription = this.parentCascade.hovered()
    // Since we might have multiple competing triggers for the same cascade (e.g. a sub-cascade
    // with different data and triggers), we have to delay it by a tick to ensure that
    // it won't be closed immediately after it is opened.
    .pipe(
      filter(active => active === this.cascadeItemInstance),
      delay(0, asapScheduler),
    ).subscribe(() => {
      this.showCascade();
    });
  }

  showCascade() {
    if (this.triggersSubCascade()) {
      this.subCascade.setSubCascadePosition();
    }
    Promise.resolve().then(() => {
      this.subCascade.show();
      this.subCascadeOpen = true;
      this.closingActionsSubscription = this.cascadeClosingActions().subscribe(() => this.hideCascade());
    });
  }

  hideCascade() {
    Promise.resolve().then(() => {
      this.subCascade.hide();
      this.subCascadeOpen = false;
      this.closingActionsSubscription.unsubscribe();
    });
  }

  /** Watch for parent close and hover close */
  cascadeClosingActions() {
    const parentClose = this.parentCascade ? this.parentCascade.closed : observableOf();
    const hover = this.parentCascade ? this.parentCascade.hovered().pipe(
      filter(active => active !== this.cascadeItemInstance),
      filter(() => this.subCascadeOpen),
    ) : observableOf();

    return merge(parentClose, hover);
  }
}
